package com.onlocation.util;

import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;

/**
 * Author: Steve Carrell
 * Date: 5/12/12
 */
public class CoordinateUtil {
    public static final String CLASS_NAME = CoordinateUtil.class.getName();
    public static final double R_EARTH = 6371000.0; // meters
    public static final int DEFAULT_DELAY = 60000; // 1s = 1000, 1min = 60000

    private static LocationManager locationManager = null;
    private static final LocationListener locationListener = new LocationListener() {
        public void onLocationChanged(Location location) {}
        public void onProviderDisabled(String provider) {}
        public void onProviderEnabled(String provider) {}
        public void onStatusChanged(String provider, int status, Bundle extra) {}
    };

    public static void init() {
        if (locationManager == null) {
            locationManager = (LocationManager) Util.context.getSystemService(Context.LOCATION_SERVICE);
        }
    }

    public static Double getCurrentLongitude() {
        Logger.v("obtaining current longitude", CLASS_NAME);
        Location gpsLoc = null;
        Location networkLoc = null;

        try {
            if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
                gpsLoc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            gpsLoc = null;
        }
        try {
            if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
                networkLoc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            networkLoc = null;
        }

        if (gpsLoc != null && networkLoc != null) {
            if (isBetterLocation(gpsLoc, networkLoc)) {
                return gpsLoc.getLongitude();
            } else {
                return networkLoc.getLongitude();
            }
        } else if (gpsLoc != null) {
            return gpsLoc.getLongitude();
        } else if (networkLoc != null) {
            return networkLoc.getLongitude();
        } else {
            return 0.0;
        }
    }

    public static Double getCurrentLatitude() {
        Logger.v("obtaining current latitude", CLASS_NAME);
        Location gpsLoc = null;
        Location networkLoc = null;

        try {
            if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
                gpsLoc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            gpsLoc = null;
        }
        try {
            if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
                networkLoc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            networkLoc = null;
        }

        if (gpsLoc != null && networkLoc != null) {
            if (isBetterLocation(gpsLoc, networkLoc)) {
                return gpsLoc.getLatitude();
            } else {
                return networkLoc.getLatitude();
            }
        } else if (gpsLoc != null) {
            return gpsLoc.getLatitude();
        } else if (networkLoc != null) {
            return networkLoc.getLatitude();
        } else {
            return 0.0;
        }
    }

    public static Float getCurrentAccuracy() {
        Logger.v("obtaining current accuracy", CLASS_NAME);
        Location gpsLoc = null;
        Location networkLoc = null;

        try {
            if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
                gpsLoc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            gpsLoc = null;
        }
        try {
            if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
                networkLoc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                locationManager.removeUpdates(locationListener);
            }
        } catch (Exception e) {
            networkLoc = null;
        }

        if (gpsLoc != null && networkLoc != null) {
            if (isBetterLocation(gpsLoc, networkLoc)) {
                return gpsLoc.getAccuracy();
            } else {
                return networkLoc.getAccuracy();
            }
        } else if (gpsLoc != null) {
            return gpsLoc.getAccuracy();
        } else if (networkLoc != null) {
            return networkLoc.getAccuracy();
        } else {
            return 0.0f;
        }
    }

    public static boolean inSelectedArea(double long_1, double lat_1,
                                         double long_2, double lat_2,
                                         float radius) {
        // Spherical Law of Cosines
        double distance = Math.acos(Math.sin(lat_2) * Math.sin(lat_1) +
                Math.cos(lat_2) * Math.cos(lat_1) *
                        Math.cos(long_1 - long_2)) * R_EARTH;

        return radius >= distance;
    }

    public static boolean isBetterLocation(Location location, Location currentBestLocation) {
        // From http://developer.android.com/guide/topics/location/obtaining-user-location.html
        if (location == null) {
            return false;
        } else if (currentBestLocation == null) {
            return true;
        }

        long delay = PreferenceUtil.getRefreshRate();

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > delay;
        boolean isSignificantlyOlder = timeDelta < -delay;
        boolean isNewer = timeDelta > 0;

        // If there's a significant time difference, use the one that is newer.
        if (isSignificantlyNewer) {
            return true;
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Compare accuracy.
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = StringUtil.equalsIgnoreNull(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    public static double validateLongitude(double longitude) {
        if (longitude > 180.0) {
            longitude = -180.0 - (longitude % 180.0);
        } else if (longitude < -180.0) {
            longitude = 180.0 + (longitude % 180.0);
        }

        return longitude;
    }

    public static double validateLatitude(double latitude) {
        // For those of use that get service at the north and south pole....
        if (latitude > 90.0) {
            latitude = 90.0;
        } else if (latitude < -90.0) {
            latitude = -90.0;
        }

        return latitude;
    }
}
